﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using Quail.Helpers;
using Quail.Html;
using Quail.Silverlight;

namespace Quail
{
    /// <summary>
    /// Simple Web control container - could be a web page, or an html control like a DIV, TABLE, etc
    /// </summary>
    public abstract class WebControlContainer : IQuailControl
    {
        private IQuailControl _parentControl;

        /// <summary>
        /// Dictionary of constructed controls
        /// </summary>
        protected readonly Hashtable HashControls = new Hashtable();

        /// <summary>
        /// Initializes a new instance of the <see cref="WebControlContainer"/> class.
        /// </summary>
        protected WebControlContainer()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="WebControlContainer"/> class.
        /// </summary>
        /// <param name="container">The control container.</param>
        protected WebControlContainer(IQuailControl container)
        {
            ParentControl = container;
        }

        public abstract WebPage Page { get; }

        /// <summary>
        /// Gets the browser object
        /// </summary>
        public abstract WebBrowser Browser
        {
            get;
        }

        public abstract Task ActiveTask { get; }
        public abstract bool Visible { get; }
        public abstract bool Exists { get; }
        public abstract bool Enabled { get; }

        /// <summary>
        /// Gets the selector for the container
        /// </summary>
        public abstract string Selector
        {
            get; set;
        }

        /// <summary>
        /// Gets the ParentControl for this control
        /// </summary>
        public IQuailControl ParentControl
        {
            get
            {
                return _parentControl;
            }
            
            set
            {
                var oldvalue = _parentControl;
                _parentControl = value;

                if (OnParentControlChanged != null)
                    OnParentControlChanged(oldvalue, _parentControl);
            }
        }


        /// <summary>
        /// Occurs when ParentControl property is changed
        /// </summary>
        public event Action<IQuailControl, IQuailControl> OnParentControlChanged;

        /// <summary>
        /// ToString() implementation writes the object's Selector and Type full name
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Selector: [" + Selector + "] of [" + GetType().FullName + "]";
        }

        /// <summary>
        /// Gets a random control of a specific type using a specific selector (may be cached).  An array of selectors is passed and only one item is returned
        /// </summary>
        /// <typeparam name="T">The type of the control</typeparam>
        /// <param name="selectors">The jQuery selectors for the control</param>
        /// <returns>The control</returns>
        public T GetRandom<T>(params string[] selectors)
            where T : WebControl, new()
        {
            return GetControls<T>(selectors).RandomItem();
        }

        /// <summary>
        /// Gets a control of a specific type using a specific selector (may be cached)
        /// </summary>
        /// <typeparam name="T">The type of the control</typeparam>
        /// <param name="selector">The jQuery selector for the control</param>
        /// <returns>The control</returns>
        public T Get<T>(string selector)
            where T : WebControl, new()
        {
            return Get((Action<T>)null, selector);
        }

        /// <summary>
        /// Gets a control of a specific type using a specific selector (may be cached)
        /// </summary>
        /// <typeparam name="T">The type of the control</typeparam>
        /// <param name="postCreate">Action to perform on object after control is constructed</param>
        /// <param name="selector">The jQuery selector for the control</param>
        /// <returns>The control</returns>
        public T Get<T>(Action<T> postCreate, string selector)
            where T : WebControl, new()
        {
            return Get((con, sel) =>
                           {
                               var result = new T
                                                {
                                                    ParentControl = con,
                                                    MySelector = sel
                                                };

                               if (postCreate != null)
                                   postCreate(result);

                               return result;
                           }, 
                           selector);

        }

        /// <summary>
        /// Gets a control of a specific type using a specific selector (may be cached)
        /// </summary>
        /// <typeparam name="T">The type of the control</typeparam>
        /// <param name="creator">Function to perform to create the control</param>
        /// <param name="selector">The jQuery selector for the control</param>
        /// <returns>The control</returns>
        public T Get<T>(Func<WebControlContainer, string, T> creator, string selector)
        {
            var key = string.Format("#{0}#{1}", typeof(T).Name, selector);
            var result = (T)HashControls[key];

            if (Equals(result, default(T)))
            {
                result = creator(this, selector);
                HashControls[key] = result;
            }

            return result;
        }

        /// <summary>
        /// Returns a list of all the links on the page
        /// </summary>
        /// <returns>A list of WebLink objects</returns>
        public List<WebLink> GetLinks()
        {
            return GetControls<WebLink>("a[href]");
        }

        /// <summary>
        /// Returns a list of controls that match the jQuery selector
        /// </summary>
        /// <param name="creator">factory function to create the control</param>
        /// <param name="selectors">The jQuery selectors to use</param>
        /// <returns>A list of WebControl objects</returns>
        public List<T> GetControls<T>(Func<WebControlContainer, string, T> creator, params string[] selectors)
            where T : WebControl
        {
            var task = ActiveTask.BeginTask("GetControls");
            try
            {
                var result = new List<T>();
                var mySelector = Selector;
                var startidx = mySelector.Length;
                if (mySelector.Length > 0)
                {
                    mySelector += " ";
                    startidx++;
                }

                foreach (var selector in selectors)
                {
                    var list = ForEachControlNode(mySelector + selector, sel => creator(this, sel.Substring(startidx)));

                    result.AddRange(list);
                }

                task.AddAttribute("Matched", result.Count.ToString());
                task.Finish();
                return result;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }


        }

        /// <summary>
        /// Returns a list of controls that match the jQuery selector
        /// </summary>
        /// <param name="selectors">The jQuery selectors to use</param>
        /// <returns>A list of WebControl objects</returns>
        public List<T> GetControls<T>(params string[] selectors)
            where T : WebControl, new()
        {
            return GetControls((container, sel) => new T
                                                       {
                                                           ParentControl = container,
                                                           MySelector = sel
                                                       },
                               selectors);
        }

        /// <summary>
        /// Returns a WebInput control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebInput control</returns>
        public WebInput GetInput(string selector)
        {
            return Get<WebInput>(selector);
        }

        /// <summary>
        /// Returns a WebInputFile control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebInputFile control</returns>
        public WebInputFile GetInputFile(string selector)
        {
            return Get<WebInputFile>(selector);
        }

        /// <summary>
        /// Returns a WebInputHtml control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <param name="inputHtmlControlType">The HTML Input type</param>
        /// <returns>A WebInputHtml control</returns>
        public WebInputHtml GetInputHtml(string selector, WebInputHtml.InputHtmlControlTypes inputHtmlControlType)
        {
            return Get<WebInputHtml>(x => x.InputHtmlControlType = inputHtmlControlType, selector);
        }

        /// <summary>
        /// Returns a WebLink control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebLink control</returns>
        public WebLink GetLink(string selector)
        {
            return Get<WebLink>(selector);
        }

        /// <summary>
        /// Returns a WebLinkByText control using the given link text
        /// </summary>
        /// <param name="text">The link text.</param>
        /// <returns>A WebLinkByText control</returns>
        public WebLinkByText GetLinkByText(string text)
        {
            return Get((c, s) => new WebLinkByText(c, s), text);
        }

        /// <summary>
        /// Returns a WebLinkByText control using the given link text and index
        /// </summary>
        /// <param name="text">The link text.</param>
        /// <param name="index">The link's index</param>
        /// <returns>A WebLinkByText control</returns>
        public WebLinkByText GetLinkByText(string text, int index)
        {
            return Get((c, s) => new WebLinkByText(c, text, index), text + "#" + index);
        }

        /// <summary>
        /// Returns a WebButton control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebButton control</returns>
        public WebButton GetButton(string selector)
        {
            return Get<WebButton>(selector);
        }

        /// <summary>
        /// Returns a WebControl control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebControl control</returns>
        public WebControl GetControl(string selector)
        {
            return Get<WebControl>(selector);
        }

        /// <summary>
        /// Returns a WebUnorderedList control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebUnorderedList control</returns>
        public WebUnorderedList GetUnorderedList(string selector)
        {
            return Get<WebUnorderedList>(selector);
        }

        /// <summary>
        /// Returns a WebImage control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebImage control</returns>
        public WebImage GetImage(string selector)
        {
            return Get<WebImage>(selector);
        }

        /// <summary>
        /// Returns a WebCheckBox control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebCheckBox control</returns>
        public WebCheckBox GetCheckBox(string selector)
        {
            return Get<WebCheckBox>(selector);
        }

        /// <summary>
        /// Returns a WebList control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebList control</returns>
        public WebList GetList(string selector)
        {
            return Get<WebList>(selector);
        }

        /// <summary>
        /// Returns a WebTable control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebTable control</returns>
        public WebTable GetTable(string selector)
        {
            return Get<WebTable>(selector);
        }

        /// <summary>
        /// Returns a WebRadioGroup control for the given jQuery selector
        /// </summary>
        /// <param name="selector">The jQuery selector.</param>
        /// <returns>A WebRadioGroup control</returns>
        public WebRadioGroup GetRadioGroup(string selector)
        {
            return Get<WebRadioGroup>(selector);
        }

        /// <summary>
        /// Returns a SlvContainer control for the given Silveright control
        /// </summary>
        /// <param name="slvObjectName">The name/id of the Silverlight control.</param>
        /// <returns>A SlvContainer control</returns>
        public SlvContainer GetSlvObject(string slvObjectName)
        {
            return Get((c, s) => new SlvContainer(c, s), slvObjectName);
        }

        /// <summary>
        /// Returns a list of controls that match the jQuery selector
        /// </summary>
        /// <param name="jquerySelector">The jQuery selector to use</param>
        /// <param name="createcontrol">A delegate to create the new control instance</param>
        /// <typeparam name="T">A type of WebControl</typeparam>
        /// <returns>A list of WebControl instances</returns>
        private List<T> ForEachControlNode<T>(string jquerySelector, Func<string, T> createcontrol) where T : WebControl
        {
            var task = ActiveTask.BeginTask("ForEachControlNode");
            task.AddAttribute("Selector", jquerySelector);
            try
            {
                var list = new List<T>();
                var xml = Browser.DoQuick(() => Browser.DoCommand("getAllElementsAsXML", jquerySelector));

                if (!string.IsNullOrEmpty(xml))
                {
                    // load the xml to a dom
                    var dom = new XmlDocument();
                    dom.LoadXml(xml);

                    if (dom.DocumentElement != null)
                    {
                        var nodes = dom.SelectNodes("/Es/E");

                        if (nodes != null && nodes.Count > 0)
                        {
                            if (nodes.Count == 1)
                            {
                                var ctrl = createcontrol(jquerySelector);
                                list.Add(ctrl);
                            }
                            else
                            {
                                foreach (XmlNode node in nodes)
                                {
                                    var selector = jquerySelector;
                                    var index = -1;

                                    var indexAttr = node.Attributes["index"];
                                    if (indexAttr != null)
                                    {
                                        if (int.TryParse(indexAttr.Value, out index))
                                        {
                                            selector += ":eq(" + index + ")";
                                        }
                                    }

                                    var ctrl = createcontrol(selector);
                                    ctrl.Index = index;
                                    list.Add(ctrl);
                                }
                            }
                        }
                    }
                }

                task.AddAttribute("Matched", list.Count.ToString());
                task.Finish();
                return list;
            }
            catch (Exception e)
            {
                task.Finish(e);
                throw;
            }
        }
    }
}
